package com.cykj.pos.util;


import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import sun.security.rsa.RSACore;
import sun.security.rsa.RSAKeyFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;

@Component
public class FeiShunUtil {

    public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
    public static final String SIGN_TYPE_RSA = "RSA";
    public static final String SIGN_TYPE_RSA_PAD = "RSA/ECB/PKCS1Padding";

    /**
     * 企业私钥
     */
    public static String privateKey;
    @Value("${feishun.privateKey}")
    public void setPrivateKey(String privateKey) {
        FeiShunUtil.privateKey = privateKey;
    }
    /**
     * 平台公钥
     */
    public static String publicKey;
    @Value("${feishun.publicKey}")
    public void setPublicKey(String publicKey) {
        FeiShunUtil.publicKey = publicKey;
    }
    /**
     * 请求地址
     */
    public static String url;
    @Value("${feishun.url}")
    public void setUrl(String url) {
        FeiShunUtil.url = url;
    }
    /**
     * 产品id
     */
    public static String signId;
    @Value("${feishun.signId}")
    public void setSignId(String signId) {
        FeiShunUtil.signId = signId;
    }

    public static String logonUrl;
    @Value("${feishun.logonUrl}")
    public void setLogonUrl(String logonUrl) {
        FeiShunUtil.logonUrl = logonUrl;
    }

    public static String taskId;
    @Value("${feishun.taskId}")
    public void setTaskId(String taskId) {
        FeiShunUtil.taskId = taskId;
    }

    public static String applyUrl;
    @Value("${feishun.applyUrl}")
    public void setApplyUrl(String applyUrl) {
        FeiShunUtil.applyUrl = applyUrl;
    }

    public static String initiateSettUrl;
    @Value("${feishun.initiateSettUrl}")
    public void setInitiateSettUrl(String initiateSettUrl) {
        FeiShunUtil.initiateSettUrl = initiateSettUrl;
    }
    public static String username;
    @Value("${feishun.username}")
    public void setUsername(String username) {
        FeiShunUtil.username = username;
    }

    public static String password;
    @Value("${feishun.password}")
    public void setPassword(String password) {
        FeiShunUtil.password = password;
    }
    public static String querySettUrl;
    @Value("${feishun.querySettUrl}")
    public void setQuerySettUrl(String querySettUrl) {
        FeiShunUtil.querySettUrl = querySettUrl;
    }
    public static String balanceUrl;
    @Value("${feishun.balanceUrl}")
    public void setBalanceUrl(String balanceUrl) {
        FeiShunUtil.balanceUrl = balanceUrl;
    }

    /**
     * 发起请求并加密报文
     *
     * @param method 接口名称
     * @param bizContent 非公共请求参数
     * @throws Exception
     */
    public static JSONObject send(String method,String token,String bizContent) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = sdf.format(Calendar.getInstance().getTime());
        Map map = new HashMap<>();
        if (!StringUtils.isEmpty(bizContent)) {
            //加密--企业私钥
            bizContent = rsaEncryptByPrivateKey(bizContent, getPrivateKey(privateKey));
        }
        map.put("bizContent", bizContent);
        map.put("method", method);
        map.put("signId", signId);
        map.put("timestamp", timestamp);
        String signParam = buildSignString(map);
        //加签----企业私钥
        String sign = rsa256Sign(signParam, getPrivateKey(privateKey));
        map.put("sign", sign);
        String back = doPost(url + method, token, JSONObject.toJSONString(map));
        System.out.println("返回值：" + back);
        JSONObject obj = JSONObject.parseObject(back);
        String code = obj.getString("code");
        System.out.println("返回值：code" + code);
        String msg = obj.getString("msg");
        System.out.println("返回值：msg" + msg);
        String data = obj.getString("data");
        System.out.println("返回值：data" + data);
        String timestampback = obj.getString("timestamp");
        System.out.println("返回值：timestamp" + timestampback);
        String signback = obj.getString("sign");
        System.out.println("返回值：sign" + signback);
        if (!StringUtils.isEmpty(signback)) {
            //验签---平台公钥
            map.put("timestamp", timestampback);
            String contentback = buildSignString(map);
            boolean isSign = rsa256CheckContent(contentback, signback, getPublicKey(publicKey));
            System.out.println("验签结果：" + isSign);
        }
        if (!StringUtils.isEmpty(data)) {
            //解密---平台公钥
            String backm = rsaDecryptByPublicKey(data, getPublicKey(publicKey));
            System.out.println("解密后data:" + backm);
            obj.put("data",backm);
        }
        return obj;
    }

    /**
     * 发送请求
     * @param url 地址
     * @param token
     * @param json 报文
     * @return
     */
    public static String doPost(String url, String token, String json) {
        String result = "";
        try {
            org.apache.http.client.HttpClient httpclient = new DefaultHttpClient();
            HttpPost httpPost = new HttpPost(url);
            //添加header参数
            httpPost.addHeader("Authorization", token);
            httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
            //这里需要指定请求参数的编码，否则默认是ISO_8859_1
            StringEntity stringEntity = new StringEntity(json, "UTF-8");
            stringEntity.setContentType("application/json");
            httpPost.setEntity(stringEntity);
            HttpResponse response = httpclient.execute(httpPost);
            if (response != null) {
                HttpEntity resEntity = response.getEntity();
                if (resEntity != null) {
                    result = EntityUtils.toString(resEntity, "UTF-8");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e);
        }
        return result;
    }

    /**
     * 私钥
     * @param privateKey
     * @return
     */
    public static PrivateKey getPrivateKey(String privateKey) {
        String privateKeyStr = privateKey;
        try {
            return restorePrivateKey(Base64.getDecoder().decode(privateKeyStr));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公钥
     * @param publicKey
     * @return
     */
    public static PublicKey getPublicKey(String publicKey ) {
        String publicKeyStr = publicKey;
        try {
            return restorePublicKey(Base64.getDecoder().decode(publicKeyStr));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static PrivateKey restorePrivateKey(byte[] keyBytes) {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        try {
            KeyFactory factory = KeyFactory.getInstance(SIGN_TYPE_RSA);
            PrivateKey privateKey = factory
                    .generatePrivate(pkcs8EncodedKeySpec);
            return privateKey;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static PublicKey restorePublicKey(byte[] keyBytes) {
        try {
            KeyFactory mykeyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
            X509EncodedKeySpec pub_spec = new X509EncodedKeySpec(keyBytes);
            PublicKey pubKey = mykeyFactory.generatePublic(pub_spec);
            return pubKey;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 生成签名字符串
     *
     * @param params
     * @return
     */
    public static String buildSignString(Map<String, String> params) {
        if (params == null || params.size() == 0) {
            return "";
        }
        List<String> keys = new ArrayList<>(params.size());
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("bizContent")) {
                continue;
            }
            keys.add(key);
        }
        /*key按照a-z排序**/
        Collections.sort(keys);
        /*key+排序后的参数+secret**/
        StringBuilder buf = new StringBuilder();
        /*构建排序后的参数**/
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key).toString();
            /*拼接非空字段**/
            if (!StringUtils.isEmpty(value)) {
                buf.append(key + value);
            }
        }
        return buf.toString();
    }

    /**
     * rsa私钥分段加密
     *
     * @param content    待加密内容
     * @param privateKey 私钥
     * @return 密文内容
     * @throws Exception
     */
    public static String rsaEncryptByPrivateKey(String content, PrivateKey privateKey) throws Exception {
        try {
            int MAX_ENCRYPT_BLOCK = RSACore.getByteLength(RSAKeyFactory.toRSAKey(privateKey)) - 11;
            Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA_PAD);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] data = StringUtils.isEmpty("UTF-8") ? content.getBytes()
                    : content.getBytes("UTF-8");
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = Base64.getEncoder().encode(out.toByteArray());
            out.close();

            return StringUtils.isEmpty("UTF-8") ? new String(encryptedData)
                    : new String(encryptedData, "UTF-8");
        } catch (Exception e) {
            throw new Exception("EncryptContent = " + content + ",charset = " + "UTF-8",
                    e);
        }
    }

    /**
     * rsa公钥分段解密
     *
     * @param content   待解密内容
     * @param publicKey 私钥
     * @return 明文内容
     * @throws Exception
     */
    public static String rsaDecryptByPublicKey(String content, PublicKey publicKey) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA_PAD);
            int MAX_DECRYPT_BLOCK = RSACore.getByteLength(RSAKeyFactory.toRSAKey(publicKey));
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] encryptedData = StringUtils.isEmpty("UTF-8")
                    ? Base64.getDecoder().decode(content.getBytes())
                    : Base64.getDecoder().decode(content.getBytes("UTF-8"));
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();

            return StringUtils.isEmpty("UTF-8") ? new String(decryptedData)
                    : new String(decryptedData, "UTF-8");
        } catch (Exception e) {
            throw new Exception("EncodeContent = " + content + ",charset = " + "UTF-8", e);
        }
    }

    /**
     * 加签:SHA256WithRSA
     *
     * @param content
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String rsa256Sign(String content, PrivateKey privateKey) throws Exception {
        try {
            java.security.Signature signature = java.security.Signature
                    .getInstance(SIGN_SHA256RSA_ALGORITHMS);
            signature.initSign(privateKey);
            if (StringUtils.isEmpty("UTF-8")) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes("UTF-8"));
            }
            byte[] signed = signature.sign();
            Base64.Encoder encoder = Base64.getEncoder();
            byte[] encode = encoder.encode(signed);
            return new String(encode);
        } catch (Exception e) {
            throw new Exception("RSAcontent = " + content + "; charset = " + "UTF-8", e);
        }
    }

    /**
     * 验签:SHA256WithRSA
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static boolean rsa256CheckContent(String content, String sign, PublicKey publicKey) throws Exception {
        try {
            java.security.Signature signature = java.security.Signature
                    .getInstance(SIGN_SHA256RSA_ALGORITHMS);
            signature.initVerify(publicKey);
            if (StringUtils.isEmpty("UTF-8")) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes("UTF-8"));
            }
            return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
        } catch (Exception e) {
            throw new Exception("RSAcontent = " + content + ",sign=" + sign + ",charset = " + "UTF-8", e);
        }
    }
}
